"""
Copyright 2009, Thomas Dejanovic, Jay Shurtz.
 
This is free software; you can redistribute it and/or modify it
under the terms of the GNU Lesser General Public License as
published by the Free Software Foundation; either version 2.1 of
the License, or (at your option) any later version.

This software is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this software; if not, write to the Free
Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
02110-1301 USA, or see the FSF site: http://www.fsf.org.
"""

id = "$Id: hatch_property_checker.py 717 2010-09-10 07:07:33Z jayshurtz $"
# $URL: http://hatch.googlecode.com/svn/tags/taggle_release_3.0/hatch/hatch_visitors/hatch_property_checker.py $
# $Author: jayshurtz $
version = " ".join(id.split()[1:3])


import to_hatch_structure
from hatch_visitor import *
from hatch_constants import *   # Nearly all properties should be checked.


class HatchPropertyChecker(HatchVisitor):
    """ Class for checking the properties of a hatch node based on it's type.
        This is done to verify that the data being used to build a design is sane.
        This class implements all the checking-related methods that would otherwise 
        be needed by the hatch node classes themselves.

        Note: This class should check what the *user* has done, not double-check 
        what hatch is 'supposed' to have done automatically. Other visitors can 
        raise errors too.
    """
    def visit_function(self, hatchNode):
        """ Check that hatch node properties are sane, & recurse into child nodes 
            to verify the entire design.
        """
        #self.temporaryProperties.append('packable_bits')
        for node in HatchNodeIteratorDepthFirst(hatchNode):
            try:
                self.checkNodeType(node)
                self.checkChildNames(node)
                self.checkNodeVisibility(node)
                if False:
                    self.checkRegAccessTypes(node)
                    self.checkErrorCorrection(node)
                # XXX Add new check functions here.
            except KeyError, e: # Obviously we will and should throw an error if any of these properties are not defined.
                raise AssertionError, hatchNodeErrorString(node, str(e) + " property not defined.")

    def checkChildNames(self, hatchNode):
        """ All nodes must have a unique '.name' attribute, with respect only to their fellow child nodes.
        """  
        childNames = set()   # These are unique names for child scope only, not global.
        for childNode in hatchNode.childNodes:
            name = str(childNode)
            if name in childNames:
                raise AssertionError, hatchNodeErrorString(hatchNode, "All child nodes in a given scope must have a unique 'name' attribute. " + name + " found more than once.")
            childNames.add(name)

    def checkNodeType(self, hatchNode):
        """ """
        if hatchNode.properties.get(PACKED, False): # Not packed if not defined.
            for childNode in hatchNode.childNodes:
                if not childNode.properties[HATCH_LEAF]:
                    raise AttributeError, hatchNodeErrorString(hatchNode, "Packed structs can only have leaf nodes as children.")

        if not hatchNode.properties[HATCH_LEAF]:  # Use property instead of hatchNode class. All physical node classes must define this property.
            if len(hatchNode.childNodes) < 1:   # Check that this object has 1+ childNodes.
                raise AssertionError, hatchNodeErrorString(hatchNode, "Non-leaf (block) nodes must have one or more child nodes.")
        else:
            if len(hatchNode.childNodes) > 0:   # Check that this object has NO childNodes.
                raise AssertionError, hatchNodeErrorString(hatchNode, "'leaf' nodes cannot have any child nodes.")
            if False: # TODO DATA_BUS_WIDTH is defined by the target bus.  we need to define it and pass it into the checker.
                if hatchNode.properties[BIT_WIDTH] > MAX_DATA_BUS_WIDTH:
                    raise AssertionError, hatchNodeErrorString(hatchNode, "Lowest level nodes must have a bit width less than " + str(MAX_DATA_BUS_WIDTH) + ".")


    def checkNodeVisibility(self, hatchNode):
        """ Check that only ellegible nodes are invisible.
        """
        if hatchNode.properties.get(INVISIBLE, False):
            if hatchNode.parent is None:
                raise AssertionError, hatchNodeErrorString(hatchNode, "Top level node cannot be invisible.")
            if len(hatchNode.childNodes) == 0:
                raise AssertionError, hatchNodeErrorString(hatchNode, "'leaf' nodes cannot be invisible.")


    def checkRegAccessTypes(self, hatchNode):
        """ Check each of the register access types.
        """
        if hatchNode.properties.get(EXTERNAL, False):   # False if not defined.
            for incompatibleProperty in (set(hatchNode.properties.keys()) - EXTERNAL_COMPATIBLE):
                if hatchNode.property.get(incompatibleProperty, None) is not None:
                    raise AssertionError, hatchNodeErrorString(hatchNode, "The '" + EXTERNAL + "' property is incompatible with the '" + incompatibleProperty + "' property.")

        # 'auto' property.
        autoValues = hatchNode.properties.get(AUTOMATIC_ACCESS, "") # Default empty string.
        if autoValues != "":    # Property exists and is not empty.
            for char in autoValues:
                if char not in ACCESS_VALUES:   # TODO define AUTOMATIC_ACCESS_VALUES if this differs from 'hw', 'sw' ?
                    raise AssertionError, hatchNodeErrorString(hatchNode, "Invalid register access mode '" + char + "' for '" + AUTOMATIC_ACCESS + "' property.")

            # d and i can default to empty strings because it makes
            # sense for counters to auto(inc/dec) every cycle as
            # opposed to based on speciffic events.

            # TODO - if these properties exist, there really is no need to use the values in the 'auto' property.
            # TODO - what if you set 'auto_clear_on' but don't include 'c' in the 'auto' property ? (currently this is silently ignored!)
            if CLEAR_ACCESS in autoValues:    
                    if hatchNode.properties.get(AUTOMATIC_CLEAR_EXPRESSION, None) is None:
                        raise AssertionError, hatchNodeErrorString(hatchNode, "Node with '" + AUTOMATIC_ACCESS + "' property '" + \
                            CLEAR_ACCESS + "' requires a[n] '" + AUTOMATIC_CLEAR_EXPRESSION + "' property.")


            if "s" in value:
                try:
                    dummy = hatchNode.properties['auto_set_on']
                except KeyError, e:
                    raise AssertionError, hatchNodeErrorString(hatchNode, "Node with 'auto' property 's' requires an 'auto_set_on' property.")
            if "p" in value:
                try:
                    dummy = hatchNode.properties['auto_preset_on']
                except KeyError, e:
                    raise AssertionError, hatchNodeErrorString(hatchNode, "Node with 'auto' property 'p' requires an 'auto_preset_on' property.")
            if "t" in value:
                try:
                    dummy = hatchNode.properties['auto_trigger_on']
                except KeyError, e:
                    raise AssertionError, hatchNodeErrorString(hatchNode, "Node with 'auto' property 't' requires an 'auto_trigger_on' property.")


        # 'sw' property.
        try:
            value = hatchNode.properties['sw']
        except KeyError, e:
            pass    # Property does not exist, skip it.
        else:
            for char in value:
                # these are documeted at http://code.google.com/p/hatch/wiki/SoftwareRegisterAccessModes
                if char not in "rwt":  # TODO - list these in hatch_constants ?
                    raise AssertionError, hatchNodeErrorString(hatchNode, "Invalid value '" + char + "' for 'sw' property (software register access.)")
            #if "r" not in value:    # Forcing all registers to be readable by software.
            #    raise AssertionError, hatchNodeErrorString(hatchNode, "Value 'r' should have been automatically added to 'sw' property.")
            if "t" in value:
                if "w" in value:
                    raise AssertionError, hatchNodeErrorString(hatchNode, "Cannot implement a software trigger that is also writable.")
                if self.has_property('auto') or self.has_property('hw'):
                    e = "Cannot implement a software trigger in addition to the "
                    if hatchNode.has_property('auto'):
                        e += 'auto '
                    if hatchNode.has_property('hw'):
                        e += 'hw '
                    raise AssertionError, hatchNodeErrorString(hatchNode, e+'properties.')

        # 'hw' property.
        try:
            value = hatchNode.properties['hw']
        except KeyError, e:
            pass    # Property does not exist, skip it.
        else:
            for char in value:
                # thes are documented here http://code.google.com/p/hatch/wiki/HardwareRegisterAccessModes
                if char not in "rwscidp":  # TODO - list these in hatch_constants ?
                    raise AssertionError, hatchNodeErrorString(hatchNode, "Invalid value '" + char + \
                        "' for 'hw' property (hardware register access.)")

        # Verify the trigger count value.
        try:
            value = int(hatchNode.properties['trigger_count'])
        except ValueError:
            raise AssertionError, hatchNodeErrorString(hatchNode, "Property 'trigger_count' must be an integer.")
        except KeyError, e:
            pass    # Property does not exist, skip it.
        else:
            if value < 1:
                raise AssertionError, hatchNodeErrorString(hatchNode, "Invalid value '" + str(value) + \
                    "' for 'trigger_count' property (must be an integer greater than 0.)")


    def checkErrorCorrection(self, hatchNode):
        """ Check Error detection & correction properties.
        """
        errorDetectValue = hatchNode.properties.get(ERROR_DETECT, None)    # Should be an int if it exists, use 0 instead of None ?
        errorCorrectValue = hatchNode.properties.get(ERROR_CORRECT, None)

        if errorDetectValue is not None and errorCorrectValue is not None:
            raise AssertionError, hatchNodeErrorString(hatchNode, "Cannot do error correction and detection at the same time.")
        if errorDetectValue is not None:
            errorDetectValues = set([1,2])
            if errorDetectValue not in errorDetectValues:
                raise AssertionError, hatchNodeErrorString(hatchNode, "Invalid value " + repr(errorDetectValue) + \
                    " for '" + ERROR_DETECT + "'. Property must be an integer in the " + repr(errorDetectValues) + ".")
        if errorCorrectValue is not None:
            errorCorrectValues = set([1])
            if errorCorrectValue not in errorCorrectValues:
                raise AssertionError, hatchNodeErrorString(hatchNode, "Invalid value '" + repr(errorCorrectValue) + \
                    "' for '" + ERROR_CORRECT + "' property (must be an integer in the " + repr(errorCorrectValues) + ".")


# Main for test
if __name__ == "__main__":

    consoleHandler.setLevel(logging.DEBUG)

    pc = HatchPropertyChecker()

    # TODO - a much better test.

    from hatch_nodes.hatch_node_physical import HatchNodePhysical
    h0 = HatchNodePhysical('h0')
    h1 = HatchNodePhysical('h1', bit_width=3, packed=None)
    h2 = HatchNodePhysical('h2', bit_width=3, leaf=False)
    h3 = HatchNodePhysical('h3', bit_width=3, leaf=True)

    h0h1 = HatchNodePhysical('h0h1', h0, h1, bit_width=4, leaf=True)
    h2h0h1 = HatchNodePhysical('h2h0h1', h2, h0h1, bit_width=4, leaf=False)
    h3h2h0h1 = HatchNodePhysical('h3h2h0h1', h3, h2h0h1)
    #h0h1h2h3 = HatchNodePhysical('h0h1h2h3', h0, h1, h2, h3)

    hatchNodes = [h0, h1, h2, h3, h0h1, h2h0h1, h3h2h0h1]

    for hatchNode in hatchNodes:
        print hatchNode.info(recurse=False)
        try:
            pc.visit(hatchNode)
        except AssertionError, e:
            print str(e)
        except KeyError, e:
            print "KeyError: " + str(e)
        else:
            print "no error!"
        raw_input()

    pc.visit(h3h2h0h1)


